
package com.shiku.imserver.common.utils;


import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;


public class DynamicEnumUtil {
    private static ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();


    private static void setFailsafeFieldValue(Field field, Object target, Object value) throws NoSuchFieldException, IllegalAccessException {

        field.setAccessible(true);


        Field modifiersField = Field.class.getDeclaredField("modifiers");

        modifiersField.setAccessible(true);

        int modifiers = modifiersField.getInt(field);


        modifiers &= 0xFFFFFFEF;

        modifiersField.setInt(field, modifiers);


        FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);

        fa.set(target, value);

    }


    private static void blankField(Class<?> enumClass, String fieldName) throws NoSuchFieldException, IllegalAccessException {

        for (Field field : Class.class.getDeclaredFields()) {

            if (field.getName().contains(fieldName)) {

                AccessibleObject.setAccessible((AccessibleObject[]) new Field[]{field}, true);

                setFailsafeFieldValue(field, enumClass, null);

                break;

            }

        }

    }


    private static void cleanEnumCache(Class<?> enumClass) throws NoSuchFieldException, IllegalAccessException {

        blankField(enumClass, "enumConstantDirectory");

        blankField(enumClass, "enumConstants");

    }


    private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes) throws NoSuchMethodException {

        Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];

        parameterTypes[0] = String.class;

        parameterTypes[1] = int.class;

        System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);

        return reflectionFactory.newConstructorAccessor(enumClass.getDeclaredConstructor(parameterTypes));

    }


    private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues) throws Exception {

        Object[] parms = new Object[additionalValues.length + 2];

        parms[0] = value;

        parms[1] = Integer.valueOf(ordinal);

        System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);

        parms[1] = parms[parms.length - 1];

        return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));

    }


    public static <T extends Enum<?>> T addEnum(Class<T> enumType, String enumName, Class<?>[] additionalTypes, Object[] additionalValues) {

        if (!Enum.class.isAssignableFrom(enumType)) {

            throw new RuntimeException("class " + enumType + " is not an instance of Enum");

        }


        Field valuesField = null;

        Field[] fields = enumType.getDeclaredFields();

        for (Field field : fields) {

            if (field.getName().contains("$VALUES")) {

                valuesField = field;

                break;

            }

        }

        AccessibleObject.setAccessible((AccessibleObject[]) new Field[]{valuesField}, true);


        try {

            Enum[] arrayOfEnum = (Enum[]) valuesField.get(enumType);

            List<T> values = new ArrayList<>(Arrays.asList((T[]) arrayOfEnum));


            Enum enum_ = (Enum) makeEnum(enumType, enumName, values.size(), additionalTypes, additionalValues);


            values.add((T) enum_);


            setFailsafeFieldValue(valuesField, null, values.toArray((Enum[]) Array.newInstance(enumType, 0)));


            cleanEnumCache(enumType);

            return (T) enum_;

        } catch (Exception e) {

            e.printStackTrace();

            throw new RuntimeException(e.getMessage(), e);

        }

    }

}

